#include "FPCH.h"

#include "FMetricsGDI.h"

#include "FKerningTable.h"
#include "FLoader.h"

namespace Font
{

	MetricsGDI::MetricsGDI(Library* a_Library, Loader* a_Loader, Family* a_Family, const TEXTMETRICW* a_Metrics, const LOGFONTW* a_Font) : Metrics(a_Library, a_Loader, a_Family)
	{
		memset(&m_Settings, 0, sizeof(LOGFONTW));
		m_Settings.lfHeight          = 0;
		m_Settings.lfWidth           = 0;
		m_Settings.lfEscapement      = a_Font->lfEscapement;
		m_Settings.lfOrientation     = a_Font->lfOrientation;
		m_Settings.lfWeight          = a_Font->lfWeight;
		m_Settings.lfItalic          = a_Font->lfItalic;
		m_Settings.lfUnderline       = a_Font->lfUnderline;
		m_Settings.lfStrikeOut       = a_Font->lfStrikeOut;
		m_Settings.lfCharSet         = DEFAULT_CHARSET;
		m_Settings.lfOutPrecision    = OUT_DEFAULT_PRECIS;
		m_Settings.lfClipPrecision   = CLIP_DEFAULT_PRECIS;
		m_Settings.lfQuality         = CLEARTYPE_QUALITY;
		m_Settings.lfPitchAndFamily  = 34; //DEFAULT_PITCH;
		wcscpy(m_Settings.lfFaceName, a_Font->lfFaceName);

		/*m_RangeStart = (unsigned int)a_Metrics->tmFirstChar;
		m_RangeEnd = (unsigned int)a_Metrics->tmLastChar;*/

		m_Style = FSTYLE_NORMAL;

		if (a_Font->lfWeight < FW_NORMAL)
		{
			m_Style |= FWEIGHT_THIN; 
		}
		else if (a_Font->lfWeight >= FW_BOLD) 
		{ 
			m_Style |= FWEIGHT_BOLD; 
		}
		else
		{ 
			m_Style |= FWEIGHT_NORMAL; 
		}

		if (a_Font->lfItalic == TRUE) { m_Style |= FSTYLE_ITALIC; }
		if (a_Font->lfUnderline == TRUE) { m_Style |= FSTYLE_UNDERLINE; }
		if (a_Font->lfStrikeOut == TRUE) { m_Style |= FSTYLE_STRIKEOUT; }
	}

	MetricsGDI::MetricsGDI(Library* a_Library, Loader* a_Loader, Family* a_Family, unsigned int a_Style, CharMap* a_CharMap) : Metrics(a_Library, a_Loader, a_Family)
	{
		unsigned int weight = FW_NORMAL;
		if (a_Style & FWEIGHT_BOLD) { weight = FW_BOLD; }
		else if (a_Style & FWEIGHT_THIN) { weight = FW_THIN; }

		memset(&m_Settings, 0, sizeof(LOGFONTW));
		m_Settings.lfHeight          = 0;
		m_Settings.lfWidth           = 0;
		m_Settings.lfEscapement      = 0;
		m_Settings.lfOrientation     = 0;
		m_Settings.lfWeight          = weight;
		m_Settings.lfItalic          = (a_Style & FSTYLE_ITALIC) ? TRUE : FALSE;
		m_Settings.lfUnderline       = (a_Style & FSTYLE_UNDERLINE) ? TRUE : FALSE;
		m_Settings.lfStrikeOut       = (a_Style & FSTYLE_STRIKEOUT) ? TRUE : FALSE;
		m_Settings.lfCharSet         = DEFAULT_CHARSET;
		m_Settings.lfOutPrecision    = OUT_DEFAULT_PRECIS;
		m_Settings.lfClipPrecision   = CLIP_DEFAULT_PRECIS;
		m_Settings.lfQuality         = CLEARTYPE_QUALITY;
		m_Settings.lfPitchAndFamily  = 34; //DEFAULT_PITCH;
		tb::StringHelper::Copy<wchar_t, char>(
			m_Settings.lfFaceName, 
			a_Family->GetName().GetData(),
			a_Family->GetName().GetLength()
		);

		m_Style = a_Style;
	}

	MetricsGDI::~MetricsGDI()
	{

	}

	FaceHandle* MetricsGDI::CreateFace(unsigned int a_Size, unsigned int a_Options /*= 0*/, CharMap* a_Map /*= NULL*/)
	{
		// settings

		m_Quality = (a_Options & FSETTING_QUALITY_LOW) ? GGO_BITMAP : GGO_GRAY8_BITMAP;
		bool kerning = (a_Options & FSETTING_KERNING_OFF) ? false : true;
		unsigned int glyph_range = (a_Options & FSETTING_GLYPH_MARK_NONEXISTING) ? 0 : GGI_MARK_NONEXISTING_GLYPHS;

		// get handle

		HDC handle = GetDC(0);

		m_Settings.lfHeight = -MulDiv(a_Size, GetDeviceCaps(handle, LOGPIXELSY), 72);

		HFONT font_handle = CreateFontIndirectW(&m_Settings);
		HFONT font_original = (HFONT)SelectObject(handle, font_handle);

		// get font metrics

		TEXTMETRICW text_metrics;
		UINT text_metrics_result = GetTextMetricsW(handle, &text_metrics);

		//FaceHandle* result = new FaceHandle(m_Library, m_Family, a_Size, text_metrics.tmExternalLeading + text_metrics.tmHeight);
		//FaceHandle* result = m_Owner->CreateFont(a_Size, text_metrics.tmExternalLeading + text_metrics.tmHeight);
		FaceHandle* result = this->CreateFont(a_Size, text_metrics.tmExternalLeading + text_metrics.tmHeight);

		// get unicode map

		unsigned int range_start, range_end;
		CharMap* font_map = NULL;

		if (!a_Map)
		{
			font_map = m_Loader->GetCharMap(m_Family->GetName());
			if (!font_map)
			{
				font_map = new CharMap(m_Library);

				DWORD glyph_map_size = GetFontUnicodeRanges(handle, 0);
				if (glyph_map_size == 0)
				{
					LOG_ERROR("Could not get unicode ranges for font.");
					return NULL;
				}

				unsigned char* glyph_map_data = new unsigned char[glyph_map_size];
				GLYPHSET* glyph_map = (GLYPHSET*)glyph_map_data;
				GetFontUnicodeRanges(handle, glyph_map);

				for (DWORD i = 0; i < glyph_map->cRanges; i++)
				{
					WCRANGE& range = glyph_map->ranges[i];

					font_map->AddKnownRange(
						range.wcLow,
						(unsigned int)range.wcLow + range.cGlyphs
					);
				}

				delete [] glyph_map_data;

				font_map->SetDefault(text_metrics.tmDefaultChar);

				m_Loader->SetCharMap(m_Family->GetName(), font_map);
			}
		}
		else
		{
			font_map = a_Map;

			/*font_map = new CharMap();

			DWORD glyph_map_size = GetFontUnicodeRanges(handle, 0);
			if (glyph_map_size == 0)
			{
				LOG_ERROR("Could not get unicode ranges for font.");
				return NULL;
			}

			unsigned char* glyph_map_data = new unsigned char[glyph_map_size];
			GLYPHSET* glyph_map = (GLYPHSET*)glyph_map_data;
			GetFontUnicodeRanges(handle, glyph_map);

			for (DWORD i = 0; i < glyph_map->cRanges; i++)
			{
				WCRANGE& range = glyph_map->ranges[i];

				unsigned int range_start = range.wcLow;
				unsigned int range_end = (unsigned int)range.wcLow + range.cGlyphs;

				if (a_Map->GetRangeInside(range_start, range_end))
				{
					font_map->AddKnownRange(range_start, range_end);
				}
			}

			delete [] glyph_map_data;

			font_map->SetDefault(text_metrics.tmDefaultChar);*/
		}

		range_start = font_map->GetRangeStart();
		range_end = font_map->GetRangeEnd();

		result->SetCharMap(font_map);

		// check for glyphs that exist in the font

		result->SetGlyphRange(range_start, range_end);

		unsigned int range_total = range_end - range_start;

		wchar_t* glyph_test = new wchar_t[range_total + 1];
		unsigned short* glyph_result = new unsigned short[range_total];

		for (unsigned int i = 0; i < range_total; i++)
		{
			glyph_test[i] = (wchar_t)(range_start + i);
		}
		glyph_test[range_total] = 0;

		DWORD glyph_test_error = GetGlyphIndicesW(
			handle, 
			glyph_test,
			range_total,
			glyph_result,
			glyph_range
		);
		if (glyph_test_error == GDI_ERROR)
		{
			return NULL;
		}

		// get glyph data

		for (unsigned int i = 0; i < range_total; i++)
		{
			// test if this glyph is in the font

			if (glyph_result[i] == 0xFFFF) { continue; }

			unsigned int glyph_index = range_start + i;

			Glyph* glyph_write = result->GetNextGlyph(glyph_index);

			GLYPHMETRICS metrics;
			memset(&metrics, 0, sizeof(metrics));

			MAT2 transform = {
				{0, 1}, {0, 0}, 
				{0, 0}, {0, 1}
			};

			unsigned int bitmap_size = GetGlyphOutlineW(
				handle,          // hdc 
				glyph_index,     // index
				m_Quality,       // output quality
				&metrics,        // metrics for glyph
				0,               // get size
				NULL,            // buffer
				&transform       // transformation matrix
			);

			if (bitmap_size == GDI_ERROR)
			{
				continue;
			}

			// set metrics for this glyph

			tb::Pos2 position(
				metrics.gmptGlyphOrigin.x, 
				text_metrics.tmAscent - metrics.gmptGlyphOrigin.y
			);
			tb::Pos2 advance(
				metrics.gmCellIncX, 
				metrics.gmCellIncY
			);

			glyph_write->SetProperties((wchar_t)glyph_index, position, advance);

			// set bitmap (if available)

			if (bitmap_size > 0)
			{
				unsigned char* bitmap = new unsigned char[bitmap_size];

				unsigned int bitmap_error = GetGlyphOutlineW(
					handle,           // hdc 
					glyph_index,      // index
					m_Quality,        // output quality
					&metrics,         // metrics for glyph
					bitmap_size,      // size of bitmap
					bitmap,           // buffer
					&transform        // transformation matrix
				);

				glyph_write->SetBitmap(metrics.gmBlackBoxX, metrics.gmBlackBoxY, 1);

				if (m_Quality == GGO_GRAY8_BITMAP)
				{
					unsigned char* src_line = bitmap;
					unsigned int src_pitch = ((metrics.gmBlackBoxX + 3) >> 2) << 2;

					unsigned char* dst_line = glyph_write->GetBitmapData();
					unsigned int dst_pitch = metrics.gmBlackBoxX;

					for (unsigned int y = 0; y < metrics.gmBlackBoxY; y++)
					{
						unsigned char* src = src_line;
						unsigned char* dst = dst_line;

						for (unsigned int x = 0; x < metrics.gmBlackBoxX; x++)
						{
							int gray = *src++ << 2;
							if (gray > 255) { gray = 255; }
							*dst++ = (unsigned char)gray;
						}

						src_line += src_pitch;
						dst_line += dst_pitch;
					}
				}
				else if (m_Quality == GGO_BITMAP)
				{
					unsigned char* src_line = bitmap;
					unsigned int src_pitch = ((metrics.gmBlackBoxX + 31) >> 5) << 2;

					unsigned char* dst_line = glyph_write->GetBitmapData();
					unsigned int dst_pitch = metrics.gmBlackBoxX;

					for (unsigned int y = 0; y < metrics.gmBlackBoxY; y++)
					{
						int mask = 0x80;

						unsigned char* src = src_line;
						unsigned char* dst = dst_line;

						for (unsigned int x = 0; x < metrics.gmBlackBoxX; x++)
						{
							*dst++ = ((*src & mask) == mask) ? 0xFF : 0x00;

							mask >>= 1;
							if (mask <= 0)
							{
								mask = 0x80;
								src++;
							}
						}

						src_line += src_pitch;
						dst_line += dst_pitch;
					}
				}
				
				delete [] bitmap;

				glyph_write->CalculateBoundingBox();
			}
		}

		delete [] glyph_test;
		delete [] glyph_result;
		
		// kerning table

		KerningTable* table = NULL;

		if (kerning)
		{
			DWORD pairs_count = GetKerningPairsW(handle, 0, NULL);
			if (pairs_count > 0)
			{
				KERNINGPAIR* pairs = new KERNINGPAIR[pairs_count];
				GetKerningPairsW(handle, pairs_count, pairs);

				table = new KerningTable(m_Library);
				for (DWORD i = 0; i < pairs_count; i++)
				{
					table->AddEntry(
						(unsigned int)pairs[i].wFirst,
						(unsigned int)pairs[i].wSecond,
						(int)pairs[i].iKernAmount
					);
				}

				delete [] pairs;
			}
		}

		result->SetKerningTable(table);

		SelectObject(handle, font_original);
		ReleaseDC(0, handle);

		return result;
	}


}; // namespace Font